This notebook uses bash kernel: https://pypi.org/project/bash_kernel/

Source notebook: asymetric crypto jupyter interactive notebook

Problem description:

We have a public facing server that has to log some sensitive clients information (in example GDPR, HIPAA or GLBA protected).

In case of a hostile agent get access to the server we do not want it to read our clients data.

Solution:

We create a public key cryptosystem. The public key is used by the server to encrypt sensitive records.

The private key is stored in air-gapped server in case we need to read those data from the logs.

Lets start with the Rivest-Shamir-Adleman (RSA) cryptosystem.
openssl genpkey -algorithm RSA -out private_rsa_key.pem -pkeyopt rsa_keygen_bits:7680  2> /dev/null
openssl pkey -pubout -in private_rsa_key.pem -out public_rsa_key.pem  2> /dev/null
openssl rsa -in private_rsa_key.pem -check -noout
RSA key ok

Create some text. And encrypt it with public key.

echo "sensitive information" > unencrypted.txt
openssl pkeyutl -encrypt -pubin -inkey public_rsa_key.pem -in unencrypted.txt -out  rsa_encrypted.txt 
cat unencrypted.txt
echo
echo "RSA encrypted binary"
cat rsa_encrypted.txt | wc -c
sensitive information

RSA encrypted binary
960

Try to decrypt it with public key, to show that access to server is not enough to read information.

openssl pkeyutl -decrypt -pubin -inkey public_rsa_key.pem -in rsa_encrypted.txt -out  rsa_decrypted_pub.txt
A private key is needed for this operation
pkeyutl: Error initializing context

Decrypt the record with private key.

openssl pkeyutl -decrypt -inkey private_rsa_key.pem -in rsa_encrypted.txt -out  rsa_decrypted.txt
cat rsa_decrypted.txt
sensitive information
SM cryptosystem

There is a problem with RSA. Even if you can choose arbitrary big keys,

the small keys are considered unsafe and for big keys the algorithms are considered slow.

Lets then try elliptic curve based algorithms.

From openssl pespective the easiest way is just addapt the previous commands to use the SM2 schema.

openssl genpkey -algorithm SM2 -out private_sm2_key.pem 
openssl pkey -pubout -in private_sm2_key.pem -out public_sm2_key.pem

Lets encrypt the message and see that it is much smaller thatn in RSA.

openssl pkeyutl -encrypt -pubin -inkey public_sm2_key.pem -in unencrypted.txt -out  sm2_encrypted.txt 
cat unencrypted.txt
echo
echo "SM2 encrypted binary"
cat sm2_encrypted.txt | wc -c
sensitive information

SM2 encrypted binary
128

Sanity check.

openssl pkeyutl -decrypt -pubin -inkey public_sm2_key.pem -in sm2_encrypted.txt -out  sm2_decrypted_pub.txt
A private key is needed for this operation
pkeyutl: Error initializing context
openssl pkeyutl -decrypt -inkey private_sm2_key.pem -in sm2_encrypted.txt -out  sm2_decrypted.txt
cat sm2_decrypted.txt
sensitive information

The openssl documentation states that:

"The SM2 algorithm was first defined by the Chinese national standard GM/T 0003-2012 and was later standardized by ISO as ISO/IEC 14888."

So, if you are sinophobic and do not want to use SM there, you can take another aproach for usage of the elliptic keys.

Openssl 3.0 supports the EC and DSA asymetric encryption commands for signning and verification only.

But we need confidentiality not only integrity. Lets then go way around this problem.

EC with combination of assymetric/symetric encryption - ECIES.

Arbitrary elliptic key generation. We do not need explicit parameters, but they can be useful for debuging.

Lets start with creating a private key we will keep in our airgapped server and

generate a public key we will transfer to the public facing server (PFS).

openssl ecparam -name  secp112r2  -out ec_param.pem -param_enc explicit
openssl genpkey -paramfile ec_param.pem -out private_ag_key.pem
openssl ec -param_enc explicit -in private_ag_key.pem -pubout -out public_ag_key.pem
read EC key
writing EC key

On PFS we generate a salt for future usage.

openssl rand -hex 5 > saltf

Create a new private key on PFS and derive shared secret with the AG public key.

openssl ecparam -name  secp112r2  -out pfs_param.pem -param_enc explicit
openssl genpkey -paramfile pfs_param.pem -out private_pfs_key.pem


openssl pkeyutl -derive -inkey private_pfs_key.pem -peerkey public_ag_key.pem -out secret
cat secret | xxd -p > hexsecret

Use the secret and salt to derive a symmetric key.

cat hexsecret saltf | \
    xargs -n 2 bash -c \
        'openssl kdf -keylen 64 -kdfopt mac:KMAC-128 -kdfopt maclen:20 -kdfopt hexkey:$0 -kdfopt hexsalt:$1 SSKDF' > derived_key
cat unencrypted.txt
sensitive information

Use the derived key to encrypt sensitive information.

openssl enc -e -base64 -aes-256-ctr  -pbkdf2 -in unencrypted.txt -nopad -nosalt -kfile derived_key -a -out ec_encrypted.txt
cat ec_encrypted.txt | wc -c
33

Now we want to achive two things:

  1. We provide enough information in logs that private_ag_key is enough to read encrypted message.
  2. Remove enough information from PFS that it cannot decipher encrypted message.

Lets start with the point 1. To decrypt message AG will need salt and public key generated from pfs key we used to encrypt message.

Because our symetric key is derived from the public AG and the private PFS secret,

we should be able decrypt it with secret derived from private AG and public PFS.

openssl ec -param_enc explicit -in private_pfs_key.pem -pubout -out public_pfs_key.pem
openssl pkeyutl -derive -inkey private_ag_key.pem -peerkey public_pfs_key.pem -out ag_decrypt_secret
cat ag_decrypt_secret | xxd -p > hex_ag_decrypt_secret

cat hex_ag_decrypt_secret saltf | \
    xargs -n 2 bash -c \
        'openssl kdf -keylen 64 -kdfopt mac:KMAC-128 -kdfopt maclen:20 -kdfopt hexkey:$0 -kdfopt hexsalt:$1 SSKDF' > decrypt_key

diff -s hexsecret hex_ag_decrypt_secret
diff -s derived_key decrypt_key
read EC key
writing EC key
Files hexsecret and hex_ag_decrypt_secret are identical
Files derived_key and decrypt_key are identical

And lets check that we can decode our message with decrypt_key

openssl enc -d -base64 -aes-256-ctr  -pbkdf2 -in ec_encrypted.txt -nopad -nosalt -kfile decrypt_key
sensitive information

We no need to share with AG the PFS public key, encrypted message and salt.

  • The encrypted message will be for sure in log (we not going to send anything to isolated service on regular basis).

  • The PFS pair key can be created for every new encrypted record or if we want to be more efficient we can switch them every for example 5 minutes.

    The public key should be stored in logs. The private key removed together with the generated secret and derived key.

    Technically it is called ephermal key.

  • When it comes to salt it can be regenerated everytime we create a new public key or just agreed before and stored on both AG and PFS.

Lets check ensure the point 2 is acomplished.

We stated before the private key, generated secret and derived key are removed. We have access to salt, both AG and PFS public keys and the encrypted message.

Lets check we cannot decode message by generating new secret using available public keys.

openssl ecparam -name  secp112r2  -out ec_param_3.pem -param_enc explicit
openssl genpkey -paramfile ec_param_3.pem -out private_pfs_key_3.pem
openssl pkeyutl -derive -inkey private_pfs_key_3.pem -peerkey public_ag_key.pem -out secret_a
cat secret_a | xxd -p > hexsecret_a
cat hexsecret_a saltf | \
    xargs -n 2 bash -c \
        'openssl kdf -keylen 64 -kdfopt mac:KMAC-128 -kdfopt maclen:20 -kdfopt hexkey:$0 -kdfopt hexsalt:$1 SSKDF' > derived_key_a
openssl enc -d -base64 -aes-256-ctr  -pbkdf2 -in ec_encrypted.txt -nopad -nosalt -kfile derived_key_a | xxd -p
42a82e40d69f26f559b18bfb7899a9aafbf36668e95f
openssl pkeyutl -derive -inkey private_pfs_key_3.pem -peerkey public_pfs_key.pem -out secret_b
cat secret_b | xxd -p > hexsecret_b
cat hexsecret_b saltf | \
    xargs -n 2 bash -c \
        'openssl kdf -keylen 64 -kdfopt mac:KMAC-128 -kdfopt maclen:20 -kdfopt hexkey:$0 -kdfopt hexsalt:$1 SSKDF' > derived_key_b
openssl enc -d -base64 -aes-256-ctr  -pbkdf2 -in ec_encrypted.txt -nopad -nosalt -kfile derived_key_b | xxd -p
db77463ed4483efc47a5de9eeeb00ea60cf3fb4110c4

Of course we can use bigger salts and more secure protocols than secp112r2. But the ECIES method used like that should be futureproof with openssl.

Cheaper approach to ECIES

Like before but we reasig from usage of expensive pbkdf2 function.

To make algorithm a little more secure will use a randomized initialization vector per message, and change server keys time to time.

In this case we do not force PFS to generate new keys every message, and we store in logs IV and PFS Public key time to time.

Lets make AG certificates.

openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:brainpoolP256r1 -out  private_ag_key.pem
openssl pkey -pubout -in private_ag_key.pem -out public_ag_key.pem

Generate Initialization Vector

openssl rand -hex 16 > iv
cat iv
cat unencrypted.txt
5de245ad1a4c3ffae7a002fc7af3f893
sensitive information

Create shared secret for encryption.

openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:brainpoolP256r1 -out  private_pfs_key.pem
openssl pkeyutl -derive -inkey private_pfs_key.pem -peerkey public_ag_key.pem -out shared

Encrypt with initialization vector and shared secret.

cat iv | xargs -I {} openssl enc -e -base64 -aes-256-ctr -in unencrypted.txt -nosalt -kfile shared -iv {} -A \
-out eciv_encrypted.txt
cat eciv_encrypted.txt
*** WARNING : deprecated key derivation used.
Using -iter or -pbkdf2 would be better.
T0mmGtH/a+dihRWdeDjyDQEtLyMHVQ==

Create public key for PFS. Remove shared secret and private_pfs_key. Now it is impossible to decrypt the message without private AG key.

openssl pkey -pubout -in private_pfs_key.pem -out public_pfs_key.pem
rm -rf private_pfs_key.pem shared

Use private AG key, initial vector and public PFS key to recreate shared secret.

openssl pkeyutl -derive -inkey private_ag_key.pem -peerkey public_pfs_key.pem -out shared_dec

Validate that we can decrypt with recreated shared secret.

cat iv | xargs -I {} openssl enc -d -base64 \
-aes-256-ctr -in eciv_encrypted.txt -nosalt -kfile shared_dec -iv {} -A \
-out eciv_decrypted.txt
cat eciv_decrypted.txt
*** WARNING : deprecated key derivation used.
Using -iter or -pbkdf2 would be better.
sensitive information

The message can be decrypted only with access to certificate that is on airgapped server quod erat demonstrandum.

References: